/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package estrutura;

import java.util.Collections;
import java.util.LinkedList;
import java.util.Set;
import org.jgrapht.graph.DefaultDirectedGraph;
import org.jgrapht.graph.DefaultEdge;

/**
 * Classe que representa um curriculo para um dado grupo de matérias, com a distribuição das matérias em semestres, buscando reduzir o número de semestres necessários para sua conclusão.
 * <p>
 * Para obter o resultado, deve-se executar o método {@link #criaSemestres(int) } para a construção dos semestres e {@link #numeroSemestres() } para se obter o resultado do número de semestres necessários.
 * <p>
 * Segue as seguintes regras para seleção das matérias de cada semestre:
     * <ul>
     *  <li>Adiciona as matérias com maior caminho mínino de matérias para sua conclusão, enquanto for possível
     *  <li>Prossegue com as se menor caminho até que não seja possível adicionar mais matérias
     *  <li>Respeita a disponibilidade da matéria para cada semestre
     * </ul>
 * @author cdvdt
 */
public class Curriculo {
    /**
     * Armazena os semestres que compõem um curriculo
     */
    private LinkedList<Grade> semestres = new LinkedList<>();
    /**
     * Grafo que representa as dependencias entre as matérias
     */
    private Grafo requisitos;
    /**
     * Armazena o curso que está sendo analisado
     */
    private Curso curso;
    /**
     * Armazena os caminhos míninos de cada matéria que compõe o curriculo
     */
    private Caminho caminhosMinimos;

    /**
     * Constrói um objeto da classe {@code Curriculo}
     */
    public Curriculo() {
    }

    /**
     * Constrói um objeto da classe {@code Curriculo}
     * @param grafo grafo que contém as dependências entre as matérias
     * @param curso curso a ser analisado
     */
    public Curriculo(Grafo grafo, Curso curso) {
        this.requisitos = grafo;
        this.curso = curso;
        //constroe semestres
    }

    /**
     * Define o grafo que contém as dependências entre as matérias
     * @param grafo grafo que contém as dependências entre as matérias
     */
    public void setRequisitos(Grafo grafo) {
        this.requisitos = grafo;
        semestres = new LinkedList<>();
        caminhosMinimos = null;
        //constroe semestres
    }

    /**
     * Retorna o grafo que contém as dependências entre as matérias
     * @return grafo que contém as dependências entre as matérias
     */
    public Grafo getRequisitos() {
        return this.requisitos;
    }

    /**
     * Define o curso a ser analisado
     * @param curso curso a ser analisado
     */
    public void setCurso(Curso curso) {
        this.curso = curso;
    }

    /**
     * Retorna o curso a ser analisado
     * @return curso a ser analisado
     */
    public Curso getCurso() {
        return this.curso;
    }

    /**
     * Retorna o número de semestres processados
     * @return número de semestres processados
     */
    public int numeroSemestres() {
        return semestres.size();
    }

    /**
     * Cria os semestres que compõem o {@code Curriculo}
     * @param periodo período inicial para construção dos semestres
     */
    public void criaSemestres(int periodo) {
        /*copia o grafo para G*/
        DefaultDirectedGraph<Materias, DefaultEdge> G = copiaGrafo((DefaultDirectedGraph<Materias, DefaultEdge>) requisitos.getGrafo());
        caminhosMinimos = new Caminho((DefaultDirectedGraph<Materias, DefaultEdge>) requisitos.getGrafo());
        Set<Materias> restantes = G.vertexSet();
        while (!restantes.isEmpty()) {
            semestres.add(new Grade(curso.getLimiteCreditos()));
            seleciona(restantes, G, periodo);

            G.removeAllVertices(semestres.getLast().getMaterias());
            restantes = G.vertexSet();

            periodo = (periodo == 1 ? 2 : 1);
        }
    }

    /**
     * Seleciona as matérias que compõem um semestre, este mátodo contem as seguintes regras para seleção das matérias:
     * <ul>
     *  <li>Adiciona as matérias com maior caminho mínino de matérias para sua conclusão, enquanto for possível
     *  <li>Prossegue com as se menor caminho até que não seja possível adicionar mais matérias
     *  <li>Respeita a disponibilidade da matéria para cada semestre
     * </ul>
     * @param restantes grupo de matérias a serem ainda processadas
     * @param grafo grafo que representa as dependências entre as matérias
     * @param periodo período do semestre atual
     */
    private void seleciona(Set<Materias> restantes, DefaultDirectedGraph<Materias, DefaultEdge> grafo, int periodo) {
        /*
         * para atingir o semestre atual usar semestres.getLast();
         * o numero de semestres atualmente processado (contando o semestre em processamento) numeroSemestres();
         * pode ser necessário adicionar um parametro int para indicar o semestre atual
         */
        class CaminhoMateria implements Comparable<CaminhoMateria> {

            int caminho;
            Materias materia;

            CaminhoMateria(int caminho, Materias materia) {
                this.caminho = caminho;
                this.materia = materia;
            }

            @Override
            public int compareTo(CaminhoMateria o) {
                return -(this.caminho - o.caminho);
            }
            
            @Override
            public String toString () {
                return materia.toString();
            }
        }

        LinkedList<CaminhoMateria> caminhos = new LinkedList();
        for (Materias atual : restantes) {
            if (grafo.inDegreeOf(atual) == 0) {
                caminhos.add(new CaminhoMateria(caminhosMinimos.calcula(atual), atual));
            }
        }

        Collections.sort(caminhos);
        /* 
         * a lista está ordenada a partir dos comprimentos de caminhos, precisa 
         * selecionar as disciplinas de acordo com a disponibilidade de horário...
         * criar algoritmo para fazê-lo em tempo ótimo
         */
        int counter = 0;
        for (CaminhoMateria atual : caminhos) {
            if (counter + atual.materia.getCreditos() <= curso.getLimiteCreditos()) {
                outerloop:
                for (Turma turmaAtual : atual.materia.getTurma()) {
                    for (Horario horarioAtual : turmaAtual.getHorarioCollection()) {
                        if (horarioAtual.retornaperiodo() == periodo) {
                            semestres.getLast().addMateria(atual.materia);
                            counter += atual.materia.getCreditos();
                            break outerloop;
                        }
                    }
                }
            } else {
                break;
            }
        }

        int i = caminhos.size() - 1;
        while ((counter <= curso.getLimiteCreditos()) && (i >= 0)) {
            if (!semestres.getLast().getMaterias().contains(caminhos.get(i).materia)) {
                if (counter + caminhos.get(i).materia.getCreditos() <= curso.getLimiteCreditos()) {
                    outerloop:
                    for (Turma turmaAtual : caminhos.get(i).materia.getTurma()) {
                        for (Horario horarioAtual : turmaAtual.getHorarioCollection()) {
                            if (horarioAtual.retornaperiodo() == periodo) {
                                semestres.getLast().addMateria(caminhos.get(i).materia);
                                counter += caminhos.get(i).materia.getCreditos();
                                break outerloop;
                            }
                        }
                    }
                } else {
                    break;
                }
            }
            --i;
        }
    }
    
    /**
     * Seleciona as matérias que compõem um semestre, este método contem as seguintes regras para seleção das matérias:
     * <ul>
     *  <li>Adiciona as matérias com maior caminho mínino de matérias para sua conclusão, enquanto for possível
     *  <li>Prossegue com as se menor caminho até que não seja possível adicionar mais matérias
     * </ul>
     * @param restantes grupo de matérias a serem ainda processadas
     * @param grafo grafo que representa as dependências entre as matérias
     */
    private void seleciona(Set<Materias> restantes, DefaultDirectedGraph<Materias, DefaultEdge> grafo) {
        /*
         * para atingir o semestre atual usar semestres.getLast();
         * o numero de semestres atualmente processado (contando o semestre em processamento) numeroSemestres();
         * pode ser necessário adicionar um parametro int para indicar o semestre atual
         */
        class CaminhoMateria implements Comparable<CaminhoMateria> {

            int caminho;
            Materias materia;

            CaminhoMateria(int caminho, Materias materia) {
                this.caminho = caminho;
                this.materia = materia;
            }

            @Override
            public int compareTo(CaminhoMateria o) {
                return -(this.caminho - o.caminho);
            }
        }

        LinkedList<CaminhoMateria> caminhos = new LinkedList();
        for (Materias atual : restantes) {
            if (grafo.inDegreeOf(atual) == 0) {
                caminhos.add(new CaminhoMateria(caminhosMinimos.calcula(atual), atual));
            }
        }

        Collections.sort(caminhos);
        /* 
         * a lista está ordenada a partir dos comprimentos de caminhos, precisa 
         * selecionar as disciplinas de acordo com a disponibilidade de horário...
         * criar algoritmo para fazê-lo em tempo ótimo
         */
        int counter = 0;
        for (CaminhoMateria atual : caminhos) {
            if (counter + atual.materia.getCreditos() <= curso.getLimiteCreditos()) {
                semestres.getLast().addMateria(atual.materia);
                counter += atual.materia.getCreditos();
            } else {
                break;
            }
        }

        int i = caminhos.size() - 1;
        while ((counter <= curso.getLimiteCreditos()) && (i >= 0)) {
            if (!semestres.getLast().getMaterias().contains(caminhos.get(i).materia)) {
                if (counter + caminhos.get(i).materia.getCreditos() <= curso.getLimiteCreditos()) {
                    semestres.getLast().addMateria(caminhos.get(i).materia);
                    counter += caminhos.get(i).materia.getCreditos();
                    //--i;
                } else {
                    break;
                }
            }
            --i;
        }
    }


    /**
     * Copia as arestas de uma grafo para outro, mantendo os mesmos vértices
     * @param grafo grafo a ser copiado
     * @return grafo que contém a cópia
     */
    private static DefaultDirectedGraph<Materias, DefaultEdge> copiaGrafo(DefaultDirectedGraph<Materias, DefaultEdge> grafo) {
        DefaultDirectedGraph<Materias, DefaultEdge> novoGrafo = new DefaultDirectedGraph<>(DefaultEdge.class);
        for (Materias atual : grafo.vertexSet()) {
            novoGrafo.addVertex(atual);
        }
        for (DefaultEdge atual : grafo.edgeSet()) {
            novoGrafo.addEdge(grafo.getEdgeSource(atual), grafo.getEdgeTarget(atual));
        }
        return novoGrafo;
    }
}
